package com.sfx.messagequeue.mqClient;

import com.sfx.messagequeue.common.arguments.*;
import com.sfx.messagequeue.common.consumer.Consumer;
import com.sfx.messagequeue.common.exception.MQException;
import com.sfx.messagequeue.common.request.Request;
import com.sfx.messagequeue.common.returns.BasicReturns;
import com.sfx.messagequeue.common.utils.BinaryPool;
import com.sfx.messagequeue.mqServer.core.BasicProperties;
import com.sfx.messagequeue.mqServer.core.ExchangeType;

import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: sfx
 * Date: 2023-08-12
 * Time: 20:24
 */
public class Channel {
    // channel的唯一标识
    private String channelId;
    //一个Channel中会有对应的TCP Connection
    private Connection connection;
    //因为Channel会订阅队列,也就需要有队列上的回调,约定一个Channel会有一个回调
    private Consumer consumer;
    // 存放客户端接收服务器返回的响应
    // key- rid   val- rid对应连接返回的响应
    private ConcurrentHashMap<String, BasicReturns> basicReturnsMap = new ConcurrentHashMap<>();

    public Channel(String channelId, Connection connection) {
        this.channelId = channelId;
        this.connection = connection;
    }

    // 在这个方法中, 和服务器进行交互, 告知服务器, 此处客户端创建了新的 channel 了.
    public boolean createChannel() throws IOException {
        // 对于创建 Channel 操作来说, payload 就是一个 basicArguments 对象
        BasicArguments basicArguments = new BasicArguments();
        basicArguments.setChannelId(channelId);
        basicArguments.setRid(generateRid());
        byte[] payload = BinaryPool.toByteArray(basicArguments);

        Request request = new Request();
        request.setType(0x1);
        request.setLength(payload.length);
        request.setPayload(payload);

        // 构造出完整请求之后, 就可以发送这个请求了.
        connection.writeRequest(request);
        // 等待服务器的响应
        BasicReturns basicReturns = waitResult(basicArguments.getRid());
        return basicReturns.isOk();
    }

    // 期望使用这个方法来阻塞等待服务器的响应.
    private BasicReturns waitResult(String rid) {
        BasicReturns basicReturns = null;
        while ((basicReturns = basicReturnsMap.get(rid)) == null) {
            // 如果查询结果为 null, 说明包裹还没回来.
            // 此时就需要阻塞等待.
            synchronized (this) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        // 读取成功之后, 还需要把这个消息从哈希表中删除掉.
        basicReturnsMap.remove(rid);
        return basicReturns;
    }

    public void putReturns(BasicReturns basicReturns) {
        basicReturnsMap.put(basicReturns.getRid(), basicReturns);
        synchronized (this) {
            // 当前也不知道有多少个线程在等待上述的这个响应.
            // 把所有的等待的线程都唤醒.
            notifyAll();
        }
    }


    private String generateRid() {
        return "R-" + UUID.randomUUID().toString();
    }

    /**
     * 发送关闭请求
     */
    public boolean close() throws IOException, MQException {
        //1. 构造请求,我们创建channel,payLoad就是BasicArguments
        Request request = new Request();
        BasicArguments basicArguments = new BasicAckArguments();
        basicArguments.setRid(generateRid());
        basicArguments.setChannelId(channelId);
        byte[] payLoad = BinaryPool.toByteArray(basicArguments);
        request.setType(0x2);
        request.setLength(payLoad.length);
        request.setPayload(payLoad);
        //2. 发送请求
        connection.writeRequest(request);
        //3. 等待服务器的响应
        BasicReturns basicReturns = waitResult(basicArguments.getRid());
        return basicReturns.isOk();
    }

    /**
     * 创建交换机
     * @param exchangeName 交换机名
     * @param type 交换机类型
     * @param durable 持久化
     * @param autoDelete 自动删除
     * @param arguments 额外参数
     */
    public boolean exchangeDeclare(String exchangeName, ExchangeType type,boolean durable,boolean autoDelete,
                                Map<String,Object> arguments) throws MQException, IOException {
        //1. 构造请求,我们创建channel,payLoad就是exchangeDeclareArguments

        Request request = new Request();
        ExchangeDeclareArguments exchangeDeclareArguments = new ExchangeDeclareArguments();
        exchangeDeclareArguments.setRid(generateRid());
        exchangeDeclareArguments.setChannelId(channelId);
        exchangeDeclareArguments.setExchangeName(exchangeName);
        exchangeDeclareArguments.setType(type);
        exchangeDeclareArguments.setDurable(durable);
        exchangeDeclareArguments.setAutoDelete(autoDelete);
        exchangeDeclareArguments.setArguments(arguments);

        byte[] payLoad = BinaryPool.toByteArray(exchangeDeclareArguments);
        request.setType(0x3);
        request.setLength(payLoad.length);
        request.setPayload(payLoad);
        //2. 发送请求
        connection.writeRequest(request);
        //3. 等待服务器的响应
        BasicReturns basicReturns = waitResult(exchangeDeclareArguments.getRid());
        return basicReturns.isOk();
    }

    /**
     * 删除交换机
     * @param exchangeName 交换机名
     */
    public boolean exchangeDelete(String exchangeName) throws MQException, IOException {
        //1. 构造请求,我们创建channel,payLoad就是exchangeDeleteArguments
        Request request = new Request();
        ExchangeDeleteArguments exchangeDeleteArguments = new ExchangeDeleteArguments();
        exchangeDeleteArguments.setRid(generateRid());
        exchangeDeleteArguments.setChannelId(channelId);
        exchangeDeleteArguments.setExchangeName(exchangeName);
        byte[] payLoad = BinaryPool.toByteArray(exchangeDeleteArguments);
        request.setType(0x4);
        request.setLength(payLoad.length);
        request.setPayload(payLoad);
        //2. 发送请求
        connection.writeRequest(request);
        //3. 等待服务器的响应
        BasicReturns basicReturns = waitResult(exchangeDeleteArguments.getRid());
        return basicReturns.isOk();
    }

    /**
     * 创建队列
     * @param queueName 队列名
     * @param durable 持久化
     * @param autoDelete 自动删除
     * @param exclusive 是否独占
     * @param arguments 参数
     * @return 返回是否成功
     */
    public boolean queueDeclare(String queueName,boolean durable,boolean autoDelete,boolean exclusive,
                                   Map<String,Object> arguments) throws MQException, IOException {
        //1. 构造请求,我们创建channel,payLoad就是queueDeclareArguments
        Request request = new Request();
        QueueDeclareArguments queueDeclareArguments = new QueueDeclareArguments();
        queueDeclareArguments.setRid(generateRid());
        queueDeclareArguments.setChannelId(channelId);
        queueDeclareArguments.setQueueName(queueName);
        queueDeclareArguments.setExclusive(exclusive);
        queueDeclareArguments.setDurable(durable);
        queueDeclareArguments.setAutoDelete(autoDelete);
        queueDeclareArguments.setArguments(arguments);
        byte[] payLoad = BinaryPool.toByteArray(queueDeclareArguments);
        request.setType(0x5);
        request.setLength(payLoad.length);
        request.setPayload(payLoad);
        //2. 发送请求
        connection.writeRequest(request);
        //3. 等待服务器的响应
        BasicReturns basicReturns = waitResult(queueDeclareArguments.getRid());
        return basicReturns.isOk();
    }


    /**
     * 删除队列
     * @param queueName 队列名
     * @return 返回是否成功
     */
    public boolean queueDelete(String queueName) throws MQException, IOException {
        //1. 构造请求,我们创建channel,payLoad就是queueDeleteArguments
        Request request = new Request();
        QueueDeleteArguments queueDeleteArguments = new QueueDeleteArguments();
        queueDeleteArguments.setRid(generateRid());
        queueDeleteArguments.setChannelId(channelId);
        queueDeleteArguments.setQueueName(queueName);
        byte[] payLoad = BinaryPool.toByteArray(queueDeleteArguments);
        request.setType(0x6);
        request.setLength(payLoad.length);
        request.setPayload(payLoad);
        //2. 发送请求
        connection.writeRequest(request);
        //3. 等待服务器的响应
        BasicReturns basicReturns = waitResult(queueDeleteArguments.getRid());
        return basicReturns.isOk();
    }

    /**
     * 创建绑定
     * @param exchangeName 交换机名
     * @param queueName 队列名
     * @param bindingKey bindingKey
     * @return 返回是否成功
     */
    public boolean queueBind(String exchangeName,String queueName,String bindingKey) throws MQException, IOException {
        //1. 构造请求,我们创建channel,payLoad就是queueDeleteArguments
        Request request = new Request();
        QueueBindArguments queueBindArguments = new QueueBindArguments();
        queueBindArguments.setRid(generateRid());
        queueBindArguments.setChannelId(channelId);
        queueBindArguments.setQueueName(queueName);
        queueBindArguments.setBindingKey(bindingKey);
        queueBindArguments.setExchangeName(exchangeName);
        byte[] payLoad = BinaryPool.toByteArray(queueBindArguments);
        request.setType(0x7);
        request.setLength(payLoad.length);
        request.setPayload(payLoad);
        //2. 发送请求
        connection.writeRequest(request);
        //3. 等待服务器的响应
        BasicReturns basicReturns = waitResult(queueBindArguments.getRid());
        return basicReturns.isOk();
    }

    /**
     * 删除绑定
     * @param exchangeName 交换机名
     * @param queueName 队列名
     * @return 返回是否成功
     */
    public boolean queueUnBind(String exchangeName,String queueName) throws MQException, IOException {
        //1. 构造请求,我们创建channel,payLoad就是queueDeleteArguments
        Request request = new Request();
        QueueUnBindArguments queueUnBindArguments = new QueueUnBindArguments();
        queueUnBindArguments.setRid(generateRid());
        queueUnBindArguments.setChannelId(channelId);
        queueUnBindArguments.setQueueName(queueName);
        queueUnBindArguments.setExchangeName(exchangeName);
        byte[] payLoad = BinaryPool.toByteArray(queueUnBindArguments);
        request.setType(0x8);
        request.setLength(payLoad.length);
        request.setPayload(payLoad);
        //2. 发送请求
        connection.writeRequest(request);
        //3. 等待服务器的响应
        BasicReturns basicReturns = waitResult(queueUnBindArguments.getRid());
        return basicReturns.isOk();
    }


    /**
     * 发送消息
     * @param exchangeName 交换机名字
     * @param basicProperties 消息属性
     * @param routingKey routingKey
     * @param body 消息本体
     * @return 返回发送消息是否成功
     */
    public boolean basicPublish(String exchangeName, BasicProperties basicProperties,
                             String routingKey,byte[] body) throws IOException {
        //2. 发送请求
        //2.1 构造payLoad
        BasicPublishArguments basicPublishArguments = new BasicPublishArguments();
        basicPublishArguments.setBasicProperties(basicProperties);
        basicPublishArguments.setChannelId(channelId);
        basicPublishArguments.setRid(generateRid());
        basicPublishArguments.setBody(body);
        basicPublishArguments.setExchangeName(exchangeName);
        basicPublishArguments.setRoutingKey(routingKey);
        byte[] payLoad = BinaryPool.toByteArray(basicPublishArguments);

        //1. 构造请求
        Request request = new Request();
        request.setType(0x9);
        request.setLength(payLoad.length);
        request.setPayload(payLoad);

        // 2. 发送请求
        connection.writeRequest(request);
        //3. 等待服务器响应
        BasicReturns basicReturns = waitResult(basicPublishArguments.getRid());
        //4. 返回操作是否成功
        return basicReturns.isOk();
    }

    /**
     * 订阅消息
     * @param queueName 队列名字
     * @param autoAck 是否返回ack
     * @return 订阅消息是否成功
     */
    public boolean basicConsume(String queueName,boolean autoAck,Consumer consumer) throws MQException, IOException {
        // 设置对应的回调
        if (this.consumer != null) {
            throw new MQException("[Channel] basicConsume 已经存在回调了,不需要在设置");
        }
        // 设置回调
        this.consumer = consumer;
        //1. 构造请求
        Request request = new Request();
        //2. 发送请求
        //2.1 构造payLoad
        BasicConsumeArguments basicConsumeArguments = new BasicConsumeArguments();
        basicConsumeArguments.setChannelId(channelId);
        basicConsumeArguments.setRid(generateRid());
        basicConsumeArguments.setConsumerTag(channelId);
        basicConsumeArguments.setAutoAck(autoAck);
        basicConsumeArguments.setQueueName(queueName);
        byte[] payLoad = BinaryPool.toByteArray(basicConsumeArguments);
        request.setType(0xa);
        request.setLength(payLoad.length);
        request.setPayload(payLoad);
        // 2. 发送请求
        connection.writeRequest(request);

        //3. 等待服务器响应
        BasicReturns basicReturns = waitResult(basicConsumeArguments.getRid());
        //4. 返回操作是否成功
        return basicReturns.isOk();
    }


    /**
     * 确认消息
     * @param queueName 队列名
     * @param messageId 消息id
     * @return 返回确认消息是否成功
     */
    public boolean basicAck(String queueName,String messageId) throws MQException, IOException {
        //1. 构造请求
        Request request = new Request();
        //2. 发送请求
        //2.1 构造payLoad
        BasicAckArguments basicAckArguments = new BasicAckArguments();
        basicAckArguments.setChannelId(channelId);
        basicAckArguments.setRid(generateRid());
        basicAckArguments.setQueueName(queueName);
        basicAckArguments.setMessageId(messageId);
        byte[] payLoad = BinaryPool.toByteArray(basicAckArguments);
        request.setType(0xb);
        request.setLength(payLoad.length);
        request.setPayload(payLoad);
        //3. 等待服务器响应
        BasicReturns basicReturns = waitResult(basicAckArguments.getRid());
        //4. 返回操作是否成功
        return basicReturns.isOk();
    }

    public String getChannelId() {
        return channelId;
    }

    public void setChannelId(String channelId) {
        this.channelId = channelId;
    }

    public Connection getConnection() {
        return connection;
    }

    public void setConnection(Connection connection) {
        this.connection = connection;
    }

    public Consumer getConsumer() {
        return consumer;
    }

    public void setConsumer(Consumer consumer) {
        this.consumer = consumer;
    }

    public ConcurrentHashMap<String, BasicReturns> getBasicReturnsMap() {
        return basicReturnsMap;
    }

    public void setBasicReturnsMap(ConcurrentHashMap<String, BasicReturns> basicReturnsMap) {
        this.basicReturnsMap = basicReturnsMap;
    }
}